﻿using System;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Linq;
using System.Reflection;
using System.Windows.Input;
using Dispatcher.ViewModels.Base;
using Repository;

namespace Dispatcher.ViewModels
{
    /// <summary>
    /// Type View Model.
    /// </summary>
    /// <typeparam name="T">Type of entities.</typeparam>
    internal class GenericGridViewModel<T> : ViewModelBase, IDisposable where T : EntityObject
    {
        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="GenericGridViewModel&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="mainViewModel">The main view model.</param>
        public GenericGridViewModel(ModelContainer modelContainer)
        {
            ModelContainer = modelContainer;
            LoadItems();
            ApplyChanges = new RoutedCommand("ApplyChanges", GetType());
            CommandBindings.Add(new CommandBinding(ApplyChanges, ApplyChangesExecute, HasChanges));
            CommandBindings.Add(new CommandBinding(ApplicationCommands.Save, SaveChangesExecute, HasChanges));
            CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo, UndoChangesExecute, HasChanges));
        }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when [apply changes executed].
        /// </summary>
        public event EventHandler<ExtendedEventArgs> AcceptChangesExecuted;

        /// <summary>
        /// Occurs when [save changes executed].
        /// </summary>
        public event EventHandler<ExtendedEventArgs> SaveChangesExecuted;

        /// <summary>
        /// Occurs when [undo changes executed].
        /// </summary>
        public event EventHandler<ExtendedEventArgs> UndoChangesExecuted;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the model container.
        /// </summary>
        public ModelContainer ModelContainer { get; private set; }

        /// <summary>
        /// Gets or sets the apply changes.
        /// </summary>
        /// <value>
        /// The apply changes.
        /// </value>
        public ICommand ApplyChanges { get; set; }

        /// <summary>
        /// Gets the types.
        /// </summary>
        public ObservableCollection<T> Items { get; private set; }

        /// <summary>
        /// Gets the types.
        /// </summary>
        public ObservableCollection<T> DeletedItems { get; private set; }

        /// <summary>
        /// Gets or sets the object set.
        /// </summary>
        /// <value>
        /// The object set.
        /// </value>
        protected ObjectSet<T> ObjectSet { get; set; }

        /// <summary>
        /// Gets or sets the name of the object set.
        /// </summary>
        /// <value>
        /// The name of the object set.
        /// </value>
        protected string ObjectSetName { get; set; }

        #endregion

        #region Methods

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            // ModelContainer.Dispose();
        }

        /// <summary>
        /// Undoes the execute.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ApplyChangesExecute(object sender, ExecutedRoutedEventArgs e)
        {
            var args = new ExtendedEventArgs();

            try
            {
                ModelContainer.SaveChanges();
                ModelContainer.SaveChanges();
                DeletedItems.Clear();
            }
            catch (Exception exception)
            {
                args.Exception = exception;
            }

            if (AcceptChangesExecuted != null)
            {
                AcceptChangesExecuted(this, args);
            }
        }

        /// <summary>
        /// Saves the changes execute.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void SaveChangesExecute(object sender, ExecutedRoutedEventArgs e)
        {
            var args = new ExtendedEventArgs();

            try
            {
                ModelContainer.SaveChanges();
                DeletedItems.Clear();
            }
            catch (Exception exception)
            {
                args.Exception = exception;
            }

            if (SaveChangesExecuted != null)
            {
                SaveChangesExecuted(this, args);
            }
        }

        /// <summary>
        /// Undoes the execute.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void UndoChangesExecute(object sender, ExecutedRoutedEventArgs e)
        {
            ModelContainer.Refresh(RefreshMode.StoreWins, Items);

            if (UndoChangesExecuted != null)
            {
                UndoChangesExecuted(this, new ExtendedEventArgs());
            }
        }

        /// <summary>
        /// Determines whether the specified sender has changes.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void HasChanges(object sender, CanExecuteRoutedEventArgs e)
        {
            ModelContainer.DetectChanges();
            e.CanExecute = DeletedItems.Any() || Items.Any(i => i.EntityState != EntityState.Unchanged);
        }

        /// <summary>
        /// Loads the items.
        /// </summary>
        private void LoadItems()
        {
            var objectSetProperty =
                ModelContainer.GetType().GetProperties().FirstOrDefault(p => p.PropertyType == typeof(ObjectSet<T>));
            if (objectSetProperty == null)
            {
                return;
            }

            ObjectSetName = objectSetProperty.Name;
            ObjectSet =
                (ObjectSet<T>)objectSetProperty.GetValue(ModelContainer, BindingFlags.GetProperty, null, null, null);
            Items = new ObservableCollection<T>(ObjectSet);
            DeletedItems = new ObservableCollection<T>();
            Items.CollectionChanged += (sender, args) =>
                                           {
                                               if (args.OldItems != null)
                                               {
                                                   foreach (var item in args.OldItems)
                                                   {
                                                       ModelContainer.DeleteObject(item);
                                                       DeletedItems.Add((T)item);
                                                   }
                                               }

                                               if (args.NewItems != null)
                                               {
                                                   foreach (var item in args.NewItems)
                                                   {
                                                       ModelContainer.AddObject(ObjectSetName, item);
                                                   }
                                               }
                                           };
        }

        #endregion
    }
}
